﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Merging;
using Engine.Sgml.Assembling;
using Engine.Sgml.Matching;
using Engine.Sgml.Validation;
using System.Diagnostics;

namespace Engine.Sgml.Allocation {
    internal sealed class UncompletedBlocksAllocationStrategy : IAllocationStrategy {
        private ModelMatcher modelMatcher;
        private MatchAssembler matchAssembler;
        private ElementAssembler elementAssembler;

        private IList<BlockItem> uncompletedBlocks;
        private IList<BlockItem> completedBlocks;

        public UncompletedBlocksAllocationStrategy() {
            modelMatcher = new ModelMatcher();
            matchAssembler = new MatchAssembler();
            elementAssembler = new ElementAssembler();
            uncompletedBlocks = new List<BlockItem>();
            completedBlocks = new List<BlockItem>();
            }

        public void Allocate(IList<BlockItem> blocks, AllocationContext context) {
            uncompletedBlocks.Clear();
            for (int i = 0; i < blocks.Count; i++)
                if (blocks[i].AncestorUncompleted)
                    uncompletedBlocks.Add(blocks[i]);
            if (uncompletedBlocks.Count == 0)
                return;
            IAllocationStrategy strategy = context.GetStrategy("BlocksAllocation");
            InitializeCompletedBlocks(context.Blocks);
            do {
                if (completedBlocks.Count != 0)
                    strategy.Allocate(completedBlocks, context);
                AssembleUncompletedBlocks(uncompletedBlocks);
                strategy.Allocate(uncompletedBlocks, context);
                InitializeCompletedBlocks(context.Blocks);
                if (completedBlocks.Count != 0) {
                    strategy.Allocate(completedBlocks, context);
                    InitializeCompletedBlocks(context.Blocks);
                    }
                MatchCreatedElements(uncompletedBlocks);
                } while (uncompletedBlocks.Count != 0 && completedBlocks.Count != 0);
            // If there are not block items
            // build uncompleted elements and break
            BuildUncompletedBlocks(uncompletedBlocks);
            }

        private void InitializeCompletedBlocks(IList<BlockItem> blocks) {
            completedBlocks.Clear();
            for (int i = 0; i < blocks.Count; i++) {
                if (!blocks[i].AncestorUncompleted) {
                    BlockItem item = blocks[i];
                    if (item.InconsistentBlocks.Count != 0)
                        completedBlocks.Add(item);
                    }
                }
            }

        private void BuildCreatedElements(IList<BlockItem> blocks) {
            for (int i = 0; i < blocks.Count; i++) {
                BlockItem element = blocks[i];
                for (int j = 0; j < element.CreatedElements.Count; j++) {
                    SElement node = element.CreatedElements[j];
                    if (elementAssembler.BuildRequired(node))
                        elementAssembler.Build(node.Declaration.ContentModel.Groups, node);
                    }
                }
            blocks.Clear();
            }

        private void BuildUncompletedBlocks(IList<BlockItem> blocks) {
            for (int i = 0; i < blocks.Count; i++) {
                SElement node = blocks[i].Ancestor;
                if (elementAssembler.BuildRequired(node))
                    elementAssembler.Build(node.Declaration.ContentModel.Groups, node);
                }
            blocks.Clear();
            }

        private void MatchCreatedElements(IList<BlockItem> blocks) {
            int count = blocks.Count;
            while (count > 0) {
                BlockItem element = blocks[0];
                blocks.RemoveAt(0);
                count--;
                Debug.Assert(element.CreatedElements != null);
                for (int j = 0; j < element.CreatedElements.Count; j++) {
                    SElement node = element.CreatedElements[j];
                    if (node.HasDeclaration) {
                        IList<Capture> captures = modelMatcher.Match(node);
                        if (MatchAssembler.HasUncompletedMatch(captures))
                            blocks.Add(new BlockItem { Ancestor = node, InconsistentBlocks = element.InconsistentBlocks, AncestorUncompleted = true });
                        }
                    }
                }
            }

        private void AssembleUncompletedBlocks(IList<BlockItem> blocks) {
            for (int i = 0; i < blocks.Count; i++) {
                BlockItem element = blocks[i];

                IList<Capture> captures = modelMatcher.Match(element.Ancestor);
                element.CreatedElements = matchAssembler.Assemble(captures);
                if (element.HasInconsistentBlocks()) {
                    IList<InconsistentBlock> inconsistentBlocks = element.InconsistentBlocks;
                    if (inconsistentBlocks.Count == 1) {
                        InconsistentBlock block = inconsistentBlocks[0];
                        Capture capture = captures.First(c => c.Entity.IsModelMatch);
                        Match match = capture.Entity.Match;
                        // Correlate first right inconsistent block and first match object
                        if (block.StartIndex == match.StartIndex || match.EndIndex == block.StartIndex) {
                            block.RecalculateStartIndex();
                            }
                        // Divide the block into the two parts
                        if (block.StartIndex < match.StartIndex && match.EndIndex < block.EndIndex) {
                            SElement previousNode = match.StartElement.PreviousNonInclusionNode();
                            for (int j = 0; j < block.Count; j++) {
                                if (object.ReferenceEquals(previousNode, block[j])) {
                                    InconsistentBlock part = new InconsistentBlock(block.Ancestor);
                                    int index = j + 1;
                                    while (block.Count != index) {
                                        Debug.Assert(part.CanAddElement(block[index]));
                                        part.Add(block[index]);
                                        block.RemoveAt(index);
                                        }
                                    inconsistentBlocks.Add(part);
                                    break;
                                    }
                                }
                            }
                        }
                    }
                blocks[i] = element;
                }
            }
        }
    }
